#pragma once
#ifndef OBR_CONFIGHELPERS_H
#define OBR_CONFIGHELPERS_H

#include "common.h"
#include "core/Matrix4x4f.h"
#include "core/Quaternion.h"
#include "core/Texture.h"
#include "core/Transform.h"

namespace OBR
{
namespace ConfigHelpers
{
/**
 * @brief load a float3 from given config
 *
 * @param config
 * @return float3
 */
inline float2 loadFloat2(Json::Value config)
{
    return make_float2(config[0].asFloat(), config[1].asFloat());
}

/**
 * @brief load a float3 from given config
 *
 * @param config
 * @return float3
 */
inline float3 loadFloat3(Json::Value config)
{
    return make_float3(config[0].asFloat(), config[1].asFloat(), config[2].asFloat());
}

/**
 * @brief load a float3 from given config
 *
 * @param config
 * @return float4
 */
inline float4 loadFloat4(Json::Value config)
{
    return make_float4(config[0].asFloat(), config[1].asFloat(), config[2].asFloat(),
                       config[3].asFloat());
}

/**
 * @brief load a spectrum value
 *
 * @param config
 * @param result
 */
inline void loadSpectrum(Json::Value config, Spectrum *result)
{
    for (int i = 0; i < Spectrum::nSamples; i++)
    {
        result[0][i] = config[i].asFloat();
    }
}

/**
 * @brief load a 2d texture
 *
 * @tparam DataType
 * @param config
 * @param tex
 */
template <class DataType> void loadTexture2D(const Json::Value &config, Texture<DataType, 2> &tex)
{
    if (config["type"].asString().compare("value") == 0)
    {
        uint size[2] = {1, 1};
        DataType data[1];
        if constexpr (std::is_same_v<DataType, Spectrum>)
        {
            loadSpectrum(config["value"], reinterpret_cast<Spectrum *>(data));
        }
        else if constexpr (std::is_same_v<DataType, float>)
        {
            data[0] = config["value"].asFloat();
        }
        else if constexpr (std::is_same_v<DataType, int>)
        {
            data[0] = config["value"].asInt();
        }
        else
        {
            std::cerr << "cannot recognize DataType given to load texture2d" << std::endl;
            std::cerr << "got:" << config << std::endl;
            throw std::runtime_error("invalid parameter");
        }

        tex.load(size, data);
    }
    else if (config["type"].asString().compare("raw file") == 0)
    {
        uint size[2];
        DataType *data = nullptr;
        RawFileIO::read_file<DataType>(config["path"].asString(), // path
                                       size[0], size[1],          // size
                                       &data                      // data
        );
        tex.load(size, data);
        delete[] data;
    }
    else if (config["type"].asString().compare("image") == 0)
    {
        auto img = cv::imread(config["path"].asString());
        uint size[2];
        size[0] = img.size[0];
        size[1] = img.size[1];
        DataType *data = new DataType[size[0] * size[1]];
        if constexpr (std::is_same_v<DataType, Spectrum>)
        {
            for (size_t i = 0; i < size[0]; i++)
            {
                for (size_t j = 0; j < size[1]; j++)
                {
                    cv::Vec3b &color = img.at<cv::Vec3b>(i, j);
                    data[(size[0] - 1 - i) * size[1] + j].fromRGB(color[2], color[1], color[0]);
                }
            }
            tex.load(size, data);
        }
        else if constexpr (std::is_same_v<DataType, float>)
        {
            cv::cvtColor(img, img, cv::COLOR_RGB2GRAY);
            for (size_t i = 0; i < size[0]; i++)
            {
                for (size_t j = 0; j < size[1]; j++)
                {
                    data[i * size[1] + j] = img.at<float>(i, j);
                }
            }
            tex.load(size, data);
        }
        else
        {
            throw std::runtime_error("invalid conversion bewteen IMAGE and " +
                                     std::string(typeid(DataType).name()));
        }
        delete[] data;
    }
    else
    {
        std::cerr << "cannot recognize GGXMaterial::albedo mode" << std::endl;
        std::cerr << "got:" << config << std::endl;
        throw std::runtime_error("invalid parameter");
    }
}

inline Matrix4x4f loadTransformMatrix(const Json::Value &config)
{
    Matrix4x4f trans(1.0f);
    for (int i = 0; i < 12; i++)
    {
        trans(i / 4, i % 4) = config[i].asFloat();
    }
    return trans;
}

inline Quaternion loadQuaternion(const Json::Value &config)
{
    return Quaternion(config[0].asFloat(), config[1].asFloat(), config[2].asFloat(),
                      config[3].asFloat());
}

inline std::shared_ptr<Transform> loadMatrixTransform(const Json::Value &config)
{
    Matrix4x4f mat = ConfigHelpers::loadTransformMatrix(config["mat"]);
    return std::make_shared<Transform>(mat);
}

inline std::shared_ptr<SRTTransform> loadSRTTransform(const Json::Value &config)
{
    return std::make_shared<SRTTransform>(ConfigHelpers::loadFloat3(config["scale"]),
                                          ConfigHelpers::loadQuaternion(config["rotation"]),
                                          ConfigHelpers::loadFloat3(config["translation"]));
}
} // namespace ConfigHelpers
} // namespace OBR

#endif
